package com.lau.videoeffect.multiplayer

import android.content.Context
import android.graphics.SurfaceTexture
import android.media.MediaPlayer
import android.net.Uri
import android.opengl.GLES11Ext
import android.opengl.GLES20
import android.opengl.Matrix
import android.view.Surface
import com.lau.videoeffect.R
import com.lau.videoeffect.utils.GLUtil
import com.lau.videoeffect.utils.Program
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer

class MovingTextureRenderer : IVideoRenderer, MediaPlayer.OnVideoSizeChangedListener, SurfaceTexture.OnFrameAvailableListener {

    companion object {
        private const val TAG = "VideoRenderer"
    }

    private val vertexData = floatArrayOf(
        1f, -1f, 0f,
        -1f, -1f, 0f,
        1f, 1f, 0f,
        -1f, 1f, 0f
    )

    private val textureVertexData = floatArrayOf(
        1f, 0f,
        0f, 0f,
        1f, 1f,
        0f, 1f
    )

    private val mMediaPlayer: MediaPlayer
    private var mSurfaceTexture: SurfaceTexture? = null
    private var mProgram: Program? = null
    private var mTextureId = 0

    private var aPosLoc = 0
    private var aTextureCoordLoc = 0
    private var uMatrixLoc = 0
    private var uSTMatrixLoc = 0

    private var uTextureSamplerLoc = 0

    private val mSTMatrix = FloatArray(16)
    private var vertexBuffer: FloatBuffer
    private var textureVertexBuffer: FloatBuffer
    private var projectionMatrix = FloatArray(16)

    private var mPlayerPrepared = false
    private var screenWidth = 0
    private var screenHeight = 0


    private var mVideoShowWidth = 0
    private val mContext: Context
    private val mScale = 0.3f
    private var mVideoRenderer: VideoRenderer? = null

    constructor(context: Context, path: String) {
        mContext = context
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(vertexData)
        vertexBuffer.position(0)

        textureVertexBuffer = ByteBuffer.allocateDirect(textureVertexData.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(textureVertexData)
        textureVertexBuffer.position(0)

        mMediaPlayer = MediaPlayer()
        mMediaPlayer.setDataSource(context, Uri.parse(path))
        mMediaPlayer.isLooping = true
        mMediaPlayer.setOnVideoSizeChangedListener(this)
    }

    override fun onResume() {
        if (mPlayerPrepared) {
            mMediaPlayer?.start()
        }
    }

    override fun onPause() {
        mMediaPlayer?.pause()
    }

    override fun onRelease() {
        mMediaPlayer?.stop()
        mMediaPlayer?.release()
    }

    override fun onSurfaceChanged(width: Int, height: Int) {
        screenWidth = width
        screenHeight = height

        try {
            mMediaPlayer.prepare()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        mMediaPlayer.start()
        mPlayerPrepared = true
    }

    override fun onSurfaceCreated() {
        val vertexShader = GLUtil.readFromRaw(mContext, R.raw.feathering_vs)
        val fragmentShader = GLUtil.readFromRaw(mContext, R.raw.feathering_fs)
        mProgram = Program(vertexShader, fragmentShader)
        mProgram?.let {
            aPosLoc = it.getAttribLocation("aPos")
            uMatrixLoc = it.getUniformLocation("uMatrix")
            uSTMatrixLoc = it.getUniformLocation("uSTMatrix")
            uTextureSamplerLoc = it.getUniformLocation("sTexture")
            aTextureCoordLoc = it.getAttribLocation("aTexCoord")
        }
        val textures = IntArray(1)
        GLES20.glGenTextures(1, textures, 0)

        mTextureId = textures[0]
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId)
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST.toFloat())
        mSurfaceTexture = SurfaceTexture(mTextureId)
        mSurfaceTexture!!.setOnFrameAvailableListener(this)

        val surface = Surface(mSurfaceTexture)
        mMediaPlayer.setSurface(surface)
        surface.release()
    }

    override fun onVideoSizeChanged(p0: MediaPlayer?, p1: Int, p2: Int) {
        updateProjection(p1, p2)
    }

    override fun onFrameAvailable(p0: SurfaceTexture?) {

    }

    override fun onDraw() {
        mSurfaceTexture?.getTransformMatrix(mSTMatrix)

        mSurfaceTexture?.updateTexImage()
        mProgram?.use()
        GLES20.glUniformMatrix4fv(uMatrixLoc, 1, false, projectionMatrix, 0)
        GLES20.glUniformMatrix4fv(uSTMatrixLoc, 1, false, mSTMatrix, 0)

        vertexBuffer.position(0)
        GLES20.glEnableVertexAttribArray(aPosLoc)
        GLES20.glVertexAttribPointer(aPosLoc, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer)

        textureVertexBuffer.position(0)
        GLES20.glEnableVertexAttribArray(aTextureCoordLoc)
        GLES20.glVertexAttribPointer(aTextureCoordLoc, 2, GLES20.GL_FLOAT, false, 8, textureVertexBuffer)

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId)

        GLES20.glUniform1i(uTextureSamplerLoc, 0)
        GLES20.glViewport(0, 0, screenWidth, screenHeight)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
    }

    private fun updateProjection(videoWidth: Int, videoHeight: Int) {
        val screenRatio = screenWidth.toFloat() / screenHeight.toFloat()
        val videoRatio = videoWidth.toFloat() / videoHeight.toFloat()
        //X轴一个单位的比例
        if (videoRatio > screenRatio) {
            mVideoShowWidth = screenWidth
            Matrix.orthoM(projectionMatrix, 0, -1f, 1f, -videoRatio / screenRatio, videoRatio / screenRatio, -1f, 1f)
        } else {
            mVideoShowWidth = (screenHeight * videoRatio).toInt()
            Matrix.orthoM(projectionMatrix, 0, -screenRatio / videoRatio, screenRatio / videoRatio, -1f, 1f, -1f, 1f)
        }
        mVideoShowWidth = (mVideoShowWidth * mScale).toInt()
        Matrix.scaleM(projectionMatrix, 0, mScale, mScale, 0f)
    }

    fun setVideoRenderer(renderer: VideoRenderer) {
        mVideoRenderer = renderer
    }
}